/*
 *  Copyright 2019-2020 Zheng Jie
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package woaini.wind.modules.security.config;

import cn.hutool.core.collection.CollUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import woaini.wind.modules.security.config.bean.SecurityProperties;
import woaini.wind.modules.security.security.JwtAccessDeniedHandler;
import woaini.wind.modules.security.security.JwtAuthenticationEntryPoint;
import woaini.wind.modules.security.security.TokenFilter;
import woaini.wind.modules.security.security.TokenProvider;
import woaini.wind.modules.security.service.OnlineUserService;
import woaini.wind.modules.security.service.UserCacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.filter.CorsFilter;

/**
 * @author Zheng Jie
 */
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SpringSecurityConfig {

  private final TokenProvider tokenProvider;
  private final CorsFilter corsFilter;
  private final JwtAuthenticationEntryPoint authenticationErrorHandler;
  private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
  private final ApplicationContext applicationContext;
  private final SecurityProperties properties;
  private final OnlineUserService onlineUserService;
  private final UserCacheManager userCacheManager;

  @Bean
  GrantedAuthorityDefaults grantedAuthorityDefaults() {
    // 去除 ROLE_ 前缀
    return new GrantedAuthorityDefaults("");
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    // 密码加密方式
    return new BCryptPasswordEncoder();
  }

  @Bean
  SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

    // 获取匿名标记
    http
        // 禁用 CSRF
        .csrf(AbstractHttpConfigurer::disable)
        .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
        // 授权异常
        .exceptionHandling(
            exceptionHandling ->
                exceptionHandling
                    .authenticationEntryPoint(authenticationErrorHandler)
                    .accessDeniedHandler(jwtAccessDeniedHandler))
        .addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class)
        .sessionManagement(
            session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        // 自定义匿名访问所有url放行：允许匿名和带Token访问，细腻化到每个 Request 类型 配置包含public的路径不需要权限
        .authorizeHttpRequests(
            (authorize) ->
                authorize
                    .requestMatchers(
                        CollUtil.newArrayList(
                                "/swagger-ui.html",
                                "/swagger-resources/**",
                                "/webjars/**",
                                "/*/api" + "-docs",
                                "/avatar/**",
                                "/api/*/public/**",
                                "/file/**",
                                "/auth/code",
                                "/auth/login",
                                "/druid/**",
                                "/wind/hook/**",
                                "/api/ai/**",
                                "/gitlab/hook/**",
                                "/qylc/hook/**",
                                "/api/ai/changeFace/*",
                                "/rasa/action/**")
                            .toArray(new String[0]))
                    .permitAll()
                    .requestMatchers(
                        HttpMethod.GET,
                        CollUtil.newArrayList(
                                "/*.html",
                                "/*/*.html",
                                "/*/*.css",
                                "/*/*.js",
                                "/webSocket/**",
                                "/api/ai/**",
                                "/api/*/public/**",
                                "/rasa/action/**")
                            .toArray(new String[0]))
                    .permitAll()
                    .requestMatchers(HttpMethod.OPTIONS)
                    .permitAll()
                    .anyRequest()
                    .authenticated());

    return http.build();
  }

  public TokenFilter tokenFilter() {
    return new TokenFilter(tokenProvider, properties, onlineUserService, userCacheManager);
  }
}
